/**
 * Copyright (c) 2012-2014, Yang Yang <cntototo@gmail.com>
 *
 * This software may be distributed under the terms of the New BSD License.
 * See README for more details.
 */

#include "daemon.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LOG_TAG "DAEMON"

#include "include.h"
#include "global.h"
#include "def.h"
#include "util.h"
#include "log.h"
#include "loop.h"
#include "dispatcher.h"
#include "if_manager.h"

#define WFS_UDP_CMD_PORT 7262
#define WFS_UDP_EVT_PORT 7263

/***********************************************************
 *                   Local functions
 ***********************************************************
 */
static void tokenize_cmd(char *cmd_str, struct wfs_cmd *cmd)
{
	/**
	 * Current implementation assumes the command format is:
	 *
	 * command param1=value1 param2=value2 ...
	 */

	int cnt = 0;
	char *tok = NULL;

	assert(cmd_str && cmd);

	tok = strtok(cmd_str, PARAM_DELIM);
	while (tok) {
		if (cnt == 0)
			cmd->cmd = str_strip(tok);
		else
			cmd->params[cnt - 1] = str_strip(tok);
		++cnt;
		tok = strtok(NULL, PARAM_DELIM);
	}

	cmd->param_num = cnt - 1;
}


static void cmd_sock_recv(int cmd_sock, void *ctx)
{
	struct wfs_global *global = ctx;
	int recv_cnt = 0;
	char buf[SMALL_BUF_LEN];
	struct sockaddr_in from;
	socklen_t fromlen = sizeof(from);
	struct wfs_cmd cmd;
	char *resp = NULL;
	int resp_len = 0;
	struct wfs_iface *cur_if = NULL;
	struct wfs_iface_input input;

	assert(ctx);

	ENTER();

	memset(buf, 0, sizeof(buf));
	memset(&cmd, 0, sizeof(cmd));
	memset(&input, 0, sizeof(input));

	recv_cnt = recvfrom(cmd_sock, buf, sizeof(buf) - 1, 0,
			(struct sockaddr *) &from, &fromlen);
	if (recv_cnt < 0) {
		wfs_perror("[cmd_sock] recvfrom");
		wfs_printf(LOG_ERR, "%s", err_to_str(WFS_INTERNAL_ERR_OS));
		goto out;
	}
	if (from.sin_addr.s_addr != inet_addr("127.0.0.1")) {
		wfs_printf(LOG_WARN, "Ignore packet from unexpected source %s",
			inet_ntoa(from.sin_addr));
		goto out;
	}
	buf[recv_cnt] = 0;

	strncpy(cmd.str, buf, sizeof(cmd.str) - 1);
	tokenize_cmd(buf, &cmd);

	input.cmd_port_dest = from.sin_port;
	/** Process REGISTER_IF and UNREGISTER_IF specifically */
	if (IS_CMD(cmd.cmd, REGISTER_IF)) {
		if (cmd.param_num == 1) {
			input.ifname = strchr(cmd.params[0], KEY_VALUE_DELIM)
					+ 1;
			if (wfs_iface_add(&input, global)) {
				wfs_printf(LOG_ERR,
					"Failed to add interface %s",
					input.ifname);
				resp = build_fail_resp();
			} else {
				resp = build_ok_resp();
			}
		} else {
			wfs_printf(LOG_ERR, "Invalid usage of command %s. "
				"Correct usage: %s", cmd.cmd,
					USG(REGISTER_IF));
			resp = build_fail_resp();
		}
		if (resp)
			resp_len = strlen(resp) + 1;
		goto send_resp;
	} else if (IS_CMD(cmd.cmd, UNREGISTER_IF)) {
		if (cmd.param_num == 1) {
			input.ifname = strchr(cmd.params[0], KEY_VALUE_DELIM)
					+ 1;
			if (wfs_iface_remove(&input)) {
				wfs_printf(LOG_ERR,
					"Failed to remove interface %s",
					input.ifname);
				resp = build_fail_resp();
			} else {
				resp = build_ok_resp();
			}
		} else {
			wfs_printf(LOG_ERR, "Invalid usage of command %s. "
				"Correct usage: %s", cmd.cmd,
					USG(UNREGISTER_IF));
			resp = build_fail_resp();
		}
		if (resp)
			resp_len = strlen(resp) + 1;
		goto send_resp;
	}

	/** Get interface info */
	cur_if = wfs_get_iface(&input);
	if (!cur_if) {
		wfs_printf(LOG_ERR, "Failed to fetch interface by"
			" command port.");
		goto out;
	}

	if (wfs_dispatch(&cmd, cur_if, &resp, &resp_len))
		wfs_printf(LOG_ERR, "Command %s failed.", cmd.cmd);

send_resp:
	if (resp) {
		sendto(cmd_sock, resp, resp_len, 0,
			(struct sockaddr *) &from, fromlen);
		FREE(resp);
	}

out:
	LEAVE();
}


static void evt_sock_recv(int evt_sock, void *ctx)
{
	struct wfs_global *global = ctx;
	int recv_cnt = 0;
	char buf[SMALL_BUF_LEN];
	struct sockaddr_in from;
	socklen_t fromlen = sizeof(from);
	struct wfs_cmd cmd;
	char *resp = NULL;
	int resp_len = 0;
	struct wfs_iface_input input;

	ENTER();

	memset(&cmd, 0, sizeof(cmd));
	memset(&input, 0, sizeof(input));

	recv_cnt = recvfrom(evt_sock, buf, sizeof(buf) - 1, 0,
			(struct sockaddr *) &from, &fromlen);
	if (recv_cnt < 0) {
		wfs_perror("[evt_sock] recvfrom");
		wfs_printf(LOG_ERR, "%s", err_to_str(WFS_INTERNAL_ERR_OS));
		goto out;
	}
	if (from.sin_addr.s_addr != inet_addr("127.0.0.1")) {
		wfs_printf(LOG_WARN, "Ignore packet from unexpected source %s",
			inet_ntoa(from.sin_addr));
		goto out;
	}
	buf[recv_cnt] = 0;

	tokenize_cmd(buf, &cmd);

	input.evt_port_dest = from.sin_port;
	/** Event socket only accepts REGISTER_IF and UNREGISTER_IF commands */
	if (IS_CMD(cmd.cmd, REGISTER_IF)) {
		if (cmd.param_num == 1) {
			input.ifname = strchr(cmd.params[0], KEY_VALUE_DELIM)
					+ 1;
			if (wfs_iface_add(&input, global)) {
				wfs_printf(LOG_ERR,
					"Failed to add interface %s",
					input.ifname);
				resp = build_fail_resp();
			} else {
				resp = build_ok_resp();
			}
		} else {
			wfs_printf(LOG_ERR, "Invalid usage of command %s. "
				"Correct usage: %s", cmd.cmd,
					USG(REGISTER_IF));
			resp = build_fail_resp();
		}
		if (resp)
			resp_len = strlen(resp) + 1;
		goto send_resp;
	} else if (IS_CMD(cmd.cmd, UNREGISTER_IF)) {
		if (cmd.param_num == 1) {
			input.ifname = strchr(cmd.params[0], KEY_VALUE_DELIM)
					+ 1;
			if (wfs_iface_remove(&input)) {
				wfs_printf(LOG_ERR,
					"Failed to remove interface %s",
					input.ifname);
				resp = build_fail_resp();
			} else {
				resp = build_ok_resp();
			}
		} else {
			wfs_printf(LOG_ERR, "Invalid usage of command %s. "
				"Correct usage: %s", cmd.cmd,
					USG(UNREGISTER_IF));
			resp = build_fail_resp();
		}
		if (resp)
			resp_len = strlen(resp) + 1;
		goto send_resp;
	}

send_resp:
	if (resp) {
		sendto(evt_sock, resp, resp_len, 0,
			(struct sockaddr *) &from, fromlen);
		FREE(resp);
	}

out:
	LEAVE();
}


static void send_event(void *ctx, u32 level, const char *evt_str, int len)
{
	struct wfs_iface *cur_if = ctx;
	char buf[EVT_BUF_LEN];
	int llen = 0;
	struct sockaddr_in dst;
	socklen_t dstlen = sizeof(dst);

	assert(ctx && evt_str);

	ENTER();

	memset(buf, 0, EVT_BUF_LEN);

	/** Construct event string to send */
	snprintf(buf, sizeof(buf) - 1, "<%d>", level);
	llen = strlen(buf);

	if (llen + len > EVT_BUF_LEN) {
		wfs_printf(LOG_WARN, "Event is too large to send.");
		goto out;
	}

	strncat(buf, evt_str, len);
	buf[EVT_BUF_LEN - 1] = 0;

	/** Construct destination address */
	dst.sin_family = AF_INET;
	dst.sin_port = cur_if->evt_port_dest;
	dst.sin_addr.s_addr = inet_addr("127.0.0.1");

	if (sendto(cur_if->global->evt_sock, buf, strlen(buf) + 1, 0,
			(struct sockaddr *) &dst, dstlen) < 0) {
		wfs_perror("[evt_sock] sendto");
		wfs_printf(LOG_ERR, "%s", err_to_str(WFS_INTERNAL_ERR_OS));
		goto out;
	}

out:
	LEAVE();
}


static const char *get_ifname(void *ctx)
{
	struct wfs_iface *cur_if = ctx;

	assert(ctx);

	return cur_if->ifname;
}


/***********************************************************
 *                   Global functions
 ***********************************************************
 */

enum wfs_internal_err wfs_daemon_init(struct wfs_global *global)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	assert(global);

	ENTER();

	/** Command socket */
	global->cmd_sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (global->cmd_sock < 0) {
		wfs_perror("[cmd_sock] socket");
		ret = WFS_INTERNAL_ERR_OS;
		wfs_printf(LOG_ERR, "%s", err_to_str(ret));
		goto out;
	}

	global->cmd_local.sin_family = AF_INET;
	global->cmd_local.sin_port = htons(WFS_UDP_CMD_PORT);
	global->cmd_local.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(global->cmd_sock, (struct sockaddr *) &global->cmd_local,
			sizeof(global->cmd_local)) < 0) {
		wfs_perror("[cmd_sock] bind");
		ret = WFS_INTERNAL_ERR_OS;
		wfs_printf(LOG_ERR, "%s", err_to_str(ret));
		goto out;
	}

	/** Event socket */
	global->evt_sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (global->evt_sock < 0) {
		wfs_perror("[evt_sock] socket");
		ret = WFS_INTERNAL_ERR_OS;
		wfs_printf(LOG_ERR, "%s", err_to_str(ret));
		goto out;
	}

	global->evt_local.sin_family = AF_INET;
	global->evt_local.sin_port = htons(WFS_UDP_EVT_PORT);
	global->evt_local.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(global->evt_sock, (struct sockaddr *) &global->evt_local,
			sizeof(global->evt_local)) < 0) {
		wfs_perror("[evt_sock] bind");
		ret = WFS_INTERNAL_ERR_OS;
		wfs_printf(LOG_ERR, "%s", err_to_str(ret));
		goto out;
	}

	/** Register cmd sock handler & evt sock handler into loop */
	ret = wfs_loop_register_read_sock(global->cmd_sock,
			cmd_sock_recv, global);
	if (ret)
		goto out;

	ret = wfs_loop_register_read_sock(global->evt_sock,
			evt_sock_recv, global);
	if (ret)
		goto out;

	/** Register get_ifname as wfs_event ifname call back */
	wfs_event_register_ifname_cb(get_ifname);

	/** Register send_event as wfs_event call back */
	wfs_event_register_cb(send_event);

out:
	if (ret) {
		wfs_printf(LOG_ERR, "Failed to initialize daemon.");
		CLOSE(global->cmd_sock);
		CLOSE(global->evt_sock);
	}
	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_daemon_deinit(struct wfs_global *global)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	assert(global);

	ENTER();

	ret = wfs_iface_remove_all();

	if (ret)
		wfs_printf(LOG_ERR, "Failed to de-initialize daemon.");
	CLOSE(global->cmd_sock);
	CLOSE(global->evt_sock);
	LEAVE();
	return ret;
}
